package cn.wolfcode.service.impl;

import cn.wolfcode.common.domain.UserInfo;
import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.CommonCodeMsg;
import cn.wolfcode.common.web.Result;
import cn.wolfcode.domain.OrderInfo;
import cn.wolfcode.domain.Product;
import cn.wolfcode.domain.SeckillProduct;
import cn.wolfcode.domain.SeckillProductVo;
import cn.wolfcode.mapper.OrderInfoMapper;
import cn.wolfcode.mapper.PayLogMapper;
import cn.wolfcode.mapper.RefundLogMapper;
import cn.wolfcode.mq.MQConstant;
import cn.wolfcode.redis.CommonRedisKey;
import cn.wolfcode.redis.SeckillRedisKey;
import cn.wolfcode.service.IOrderInfoService;
import cn.wolfcode.service.ISeckillProductService;
import cn.wolfcode.feign.ProductFeignApi;
import cn.wolfcode.util.IdGenerateUtil;
import cn.wolfcode.web.controller.OrderInfoController;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import cn.wolfcode.web.sendCallback.DefaultSendCallback;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created by wolfcode
 */
@Slf4j
@Service
public class OrderInfoSeviceImpl implements IOrderInfoService {
    @Autowired
    private ISeckillProductService seckillProductService;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private PayLogMapper payLogMapper;
    @Autowired
    private RefundLogMapper refundLogMapper;
    @Autowired
    private ProductFeignApi productFeignApi;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public OrderInfo getById(String orderNo) {
        OrderInfo order = orderInfoMapper.find(orderNo);
        if (order != null) {
            Result<Product> result = productFeignApi.getById(order.getProductId());
            String json = JSON.toJSONString(result);
            if (result.hasError()) {
                log.error("订单查询商品信息失败: {}", json);
                return null;
            }
            log.info("查询订单信息: {}", JSON.toJSONString(order));
        }
        return order;
    }

    @Override
    public OrderInfo getByUserIdAndSeckillId(Long phone, Long seckillId) {
        return orderInfoMapper.getByUserIdAndSeckillId(phone, seckillId);
    }

    @Override
    public String createOrder(Long phone, SeckillProductVo seckillProductVo) {
        // 创建锁对象
        RLock lock = redissonClient.getLock("seckill:product:stock:lock:" + seckillProductVo.getId());
        try {
            // 加锁
            lock.lock(10, TimeUnit.SECONDS);
            // 扣减库存
            int row = seckillProductService.decrStockCount(seckillProductVo.getId());
            // 判断乐观锁是否加锁成, 是否有超卖现象
            if (row <= 0) {
                throw new BusinessException(SeckillCodeMsg.SECKILL_STOCK_OVER);
            }
            // 创建订单对象
            OrderInfo orderInfo = this.create(phone, seckillProductVo);

            // 保存订单对象
            orderInfoMapper.insert(orderInfo);

            return orderInfo.getOrderNo();
        } catch (BusinessException be) {
            throw be;   // 不处理继续往外跑出
        } catch (Exception e) {
            // 重新同步 redis 库存, 设置本地库存售完标识为 false
            this.rollbackStockCount(seckillProductVo.getId());
            log.error("[创建订单] 创建订单失败: ", e);

            // 继续向外跑出异常
            throw new BusinessException(SeckillCodeMsg.REPEAT_SECKILL);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    @Override
    public String createOrder(Long phone, Long seckillId) {
        // 创建锁对象
        RLock lock = redissonClient.getLock("seckill:product:stock:lock:" + seckillId);
        SeckillProductVo vo = null;

        try {
            // 加锁
            lock.lock(10, TimeUnit.SECONDS);
            // 查询最新的库存情况
            vo = seckillProductService.queryById(seckillId);
            if (vo.getStockCount() > 0) {
                // 扣除库存
                int row = seckillProductService.decrStockCount(seckillId);
                if (row <= 0) {
                    // 乐观锁成功, 库存不足
                    throw new BusinessException(SeckillCodeMsg.SECKILL_STOCK_OVER);
                }
            }
            // 创建订单
            OrderInfo orderInfo = this.create(phone, vo);

            // 保存订单对象
            orderInfoMapper.insert(orderInfo);
            return orderInfo.getOrderNo();
        } catch (BusinessException e) {
            throw e;   // 不处理继续往外抛出
        } catch (Exception e) {
            // 从新同步 redis 库存, 设置本地库存售完标记为 false
            if (vo != null && vo.getStockCount() > 0) {
                this.rollbackStockCount(vo);
            }
            log.error("[创建订单] 创建订单失败: ", e);

            // 继续向外抛出异常
            throw new BusinessException(SeckillCodeMsg.REPEAT_SECKILL);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    private void rollbackStockCount(Long id) {
        SeckillProductVo vo = seckillProductService.queryById(id);
        // Redis 库存回补
        redisTemplate.opsForHash().put(SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.getRealKey(vo.getTime().toString()), id.toString(), vo.getStockCount());
        // 取消本地标识
        OrderInfoController.LOCAL_STOCK_OVER_FLAG_MAP.put(id, false);
        log.warn("[回滚库存] 订单创建失败, 回补 redis 库存以及取消本地售完标记");
    }

    private void rollbackStockCount(SeckillProduct sp) {
        // Redis 库存回补
        redisTemplate.opsForHash().put(SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.getRealKey(sp.getTime().toString()), sp.getId().toString(), sp.getStockCount().toString());
        // 取消本地标识
        rocketMQTemplate.asyncSend(MQConstant.CANCEL_SECKILL_OVER_SIGE_TOPIC, sp.getId(), new DefaultSendCallback("取消本地标识", sp.getId()));
        log.warn("[回滚库存] 订单创建失败, 回补 redis 库存以及取消本地售完标记");
    }


    @Override
    public boolean validTime(Date startDate, Integer time) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startDate);
        calendar.set(Calendar.HOUR_OF_DAY, time);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.MINUTE, 0);

        Date startTime = calendar.getTime();

        calendar.add(Calendar.HOUR_OF_DAY, 1);
        Date endTime = calendar.getTime();

        long now = System.currentTimeMillis();

        return startDate.getTime() <= now && now < endTime.getTime();
    }

    @Override
    public UserInfo getByToken(String token) {

        String json = redisTemplate.opsForValue().get(CommonRedisKey.USER_TOKEN.getRealKey(token));
        if (StringUtils.isEmpty(json)) {
            return null;
        }

        return JSON.parseObject(json, UserInfo.class);
    }

    @Override
    public void checkOrderTimeout(String orderNo, Long seckillId) {
        // 1. 基于订单编号查询订单对象
        // 2. 检查订单是否已经支付, 如果已支付. 直接忽略
        // 3. 修改订单状态超时取消
        int row = orderInfoMapper.updateCancelStatus(orderNo, OrderInfo.STATUS_TIMEOUT);
        if (row > 0) {
            // 4. 回滚库存(redis/mysql)
            seckillProductService.incrStockCount(seckillId);
            SeckillProductVo vo = seckillProductService.queryById(seckillId);
            // 5. 取消库存售完标记(本地标记 JVM => 分布式缓存同步 => 广播模式
            rollbackStockCount(vo);
        }
    }

    @Override
    public void payOrder(String orderNo, int payType) {
        orderInfoMapper.changePayStatus(orderNo, OrderInfo.STATUS_ACCOUNT_PAID, payType);
    }

    private OrderInfo create(Long phone, SeckillProductVo seckillProductVo) {

        Date now = new Date();
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(seckillProductVo, orderInfo);
        orderInfo.setOrderNo(IdGenerateUtil.get().nextId() + "");
        orderInfo.setCreateDate(now);
        orderInfo.setSeckillDate(now);
        orderInfo.setSeckillTime(seckillProductVo.getTime());
        orderInfo.setDeliveryAddrId(1L);  // 收货地址
        orderInfo.setProductCount(1);  // 商品数量
        orderInfo.setStatus(OrderInfo.STATUS_ARREARAGE);
        orderInfo.setSeckillId(seckillProductVo.getId());
        orderInfo.setUserId(phone);

        return orderInfo;
    }
}
